package controller.solver;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.Semaphore;

import javax.swing.Timer;

import model.BasicParameter;
import model.cnf.CNF;
import model.solver.ESolver;
import model.solver.IllegalSolverArgumentException;
import model.solver.SolverParameter;
import model.solver.SolverResult;
import controller.SessionManager;
import controller.xml.XMLBaliseFormat;

/**
 * <p>
 * Represent a solver. Override checkParameter() to put some limitation on the parameter that can be
 * use with your solver, by default it just call the {@link SolverParameter} method to check
 * parameter .
 * </p>
 * <p>
 * getCommandLine() return the list of parameter that will be use to run the solver
 * </p>
 * <p>
 * runSolver() is use to call the solver
 * </p>
 * <p>
 * hasTimeOut() is to know if your solver has some mechanism that allow it to stop itself after a
 * certain time, if not this method should return false
 * </p>
 * <p>
 * evaluate() will evaluate the CNF using the method describe previously, it will get the parameter
 * and run the solver. If the solver can't deal with the timeout, it will start a timer that will
 * destroy the processus when the timeout is over.
 * </p>
 * <p>
 * Don't forget to set set the attribute "running" at true when it is running, and use the attribute
 * "process" to store the process executing the solver in order to allow the internal stop/timeout
 * mechanism to work.
 * </p>
 * <p>
 * /!\ you must release the semaphore after the real lunch of the process. It is use for example be
 * sure that a {@link SessionManager} can't be closed before a solver has been completely lunched,
 * in this case the session will be close but the solver will still be running.
 * </p>
 * 
 * @author stephane
 * 
 */
public abstract class Solver implements ActionListener {
	/**
	 * little delay that is add to the timer to make sure that the the timer doesn't fire before the
	 * solver has been executed during all the time it was supposed to be running
	 */
	public static final int	ADD_DELAY	= 200;

	protected ESolver		type		= ESolver.UNKNOW;


	protected String		path		= "";
	protected Timer			timer		= new Timer(BasicParameter.DEFAULT_DELAY + ADD_DELAY, this);
	private boolean			stop		= false;
	private boolean			running		= false;
	private boolean			timeout		= false;
	protected Semaphore		sem			= new Semaphore(1);
	/**
	 * store the process in this attribute
	 */
	protected Process		process		= null;

	/**
	 * Export the data in XML
	 * 
	 * @param file
	 *            {@link BufferedWriter} to write
	 * @throws IOException
	 */
	public void exportXML(BufferedWriter file) throws IOException {

		// open
		// file.newLine();
		file.write("<" + XMLBaliseFormat.SOLVER + ">");

		// type
		// file.newLine();
		file.write("<" + XMLBaliseFormat.SOLVER_TYPE + ">");
		file.write(type.getXmlValue());
		file.write("</" + XMLBaliseFormat.SOLVER_TYPE + ">");

		// param
		getSolverParameter().exportXML(file);

		// close
		// file.newLine();
		file.write("</" + XMLBaliseFormat.SOLVER + ">");
	}

	/*
	 * if the solver is running
	 * 
	 * @return
	 *//*
		 * public boolean isRunning() {
		 * 
		 * try { sem.acquire(); } catch (InterruptedException e) { // TODO Auto-generated catch
		 * block e.printStackTrace(); return false; } running;
		 * 
		 * 
		 * }
		 */

	/**
	 * Return the type of the solver.
	 * 
	 * @return the type of the solver.
	 */
	public ESolver getType() {
		return type;
	}

	/**
	 * Use it to modify the running attribute /!\ not protected against concurrent access.
	 * 
	 * @param running
	 *            true if the solver is running.
	 */
	protected void setRunning(boolean running) {
		this.running = running;
	}

	/**
	 * If the solver has been stop /!\ not protected against concurrent access.
	 * 
	 * @return if the solver has been stop
	 */
	protected boolean hasBeenStop() {
		return stop;
	}

	/**
	 * If the solver has been stop by timeout /!\ not protected against concurrent access.
	 * 
	 * @return if the solver has been stop by timeout
	 */
	protected boolean hasBeenTimeout() {
		return timeout;
	}

	/**
	 * Return the path of the file associate to the solver.
	 * 
	 * @return the path
	 */
	public String getPath() {
		return path;
	}

	/**
	 * Determine if the solver has a timeout mechanism that allow him to stop after a certain time.
	 * 
	 * @return if it has one
	 */
	public abstract boolean hasTimeout();

	/**
	 * Check if the parameter are good for this solver.
	 * 
	 * @return if they are all right
	 */
	public abstract boolean checkParameter();

	/**
	 * Return the solver parameter.
	 * 
	 * @return the parameter
	 */
	public abstract SolverParameter getSolverParameter();

	/**
	 * Set the solver parameter. /!\BE CAREFULL DON'T ASSIGNED PARAMETER THAT THE SOLVER CANNOT
	 * HANDLE.
	 */
	public abstract void setSolverParameter(SolverParameter param);

	/**
	 * Return the arguments to give to the real solver.
	 * 
	 * @param cnfFile
	 *            the cnf
	 * @return a array of String equivalent to the command line you would write on a terminal
	 */
	public abstract String[] getCommandLine(String cnfFile);

	/**
	 * * Run the preprocessor with the specified arguments.
	 * 
	 * @param cmd
	 *            arguments
	 * @param manager
	 *            the semphore to release after the solver has been lunched.
	 * 
	 * @return the associate {@link SolverResult}
	 */
	public abstract SolverResult runSolver(String[] cmd, Semaphore manager);

	/**
	 * Evaluate a CNF.
	 * 
	 * @param cnf
	 *            cnf to evaluate
	 * @param manager
	 *            semaphore provide by the {@link SessionManager}
	 * @return the result of the evaluation
	 * @throws IllegalSolverArgumentException
	 *             if the arguments are not correct.
	 */
	public SolverResult evaluate(CNF cnf, Semaphore manager) throws IllegalSolverArgumentException {
		try
		{
			sem.acquire();
		} catch (InterruptedException e)
		{// pb acquire
			if (manager != null)
				manager.release();
			return null;
		}

		String[] cmdArray = null;
		File tempIn = null;

		try
		{
			if (cnf == null)
				throw new IllegalArgumentException();
			if (!checkParameter())
				throw new IllegalSolverArgumentException();

			tempIn = cnf.getCNFfile();// cnf.export(directory);
			if (tempIn == null)
				throw new IllegalArgumentException();

			cmdArray = getCommandLine(tempIn.getPath());
			if (cmdArray == null)
				throw new IllegalArgumentException();
		} catch (IllegalSolverArgumentException e)
		{
			sem.release();
			if (manager != null)
				manager.release();
			throw e;
		} catch (IllegalArgumentException e)
		{
			sem.release();
			if (manager != null)
				manager.release();
			throw e;
		}

		stop = running = timeout = false;
		process = null;

		if (getSolverParameter().needTimeOut() && !hasTimeout())
		{// gere le
			// timeout
			// manuellement
			timer = new Timer(getSolverParameter().getTimeOut() + ADD_DELAY, this);
			timer.start();
			System.out.println("demarre timer " + (getSolverParameter().getTimeOut() + ADD_DELAY));
		}

		running = true;

		SolverResult res = runSolver(cmdArray, manager);

		running = false;
		// arrete timer
		timer.stop();
		// tempIn.delete();

		return res;
	}

	/**
	 * Substitute the " " with "\ " for command argument in linux.
	 * 
	 * @param arg
	 *            the original argument
	 * @return the argument with the "\ " instead of the " "
	 */
	public static String correctFormatForLinux(String arg) {
		String res = arg.toString();
		int i = res.indexOf(' ');
		while (i != -1)
		{
			int j = 0;
			res = res.substring(0, i) + "\\ " + res.substring(i + 1);
			j = res.substring(i + 2).indexOf(' ');
			if (j == -1)
				break;
			i += j + 2;
		}

		return res;
	}

	/**
	 * Stop the execution.
	 */
	public void stop() {
		timer.stop();

		try
		{
			sem.acquire();
		} catch (InterruptedException e)
		{
			e.printStackTrace();
			return;
		}

		if (running && process != null)
		{// arrete
			System.out.println("Arrete le proc cause appelle de stop()");
			running = false;
			stop = true;
			process.destroy();
		}

		sem.release();
	}

	@Override
	public void actionPerformed(ActionEvent arg0) {

		if (arg0.getSource() == timer)
		{// doit terminer l'execution
			timer.stop();
			try
			{
				sem.acquire();
			} catch (InterruptedException e)
			{
				e.printStackTrace();
				return;
			}

			if (running && process != null)
			{// arrete
				System.out.println("Arrete le proc cause timeout");
				running = false;
				timeout = true;
				process.destroy();
			}

			sem.release();
		}
	}

	@Override
	public String toString() {
		return type.getDescription();
	}
}
